using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using ClickOnceMaster.Helpers;
using ClickOnceMaster.WizardConfiguration;
using Microsoft.Build.Tasks.Deployment.ManifestUtilities;
using ClickOnceMaster.Properties;

namespace ClickOnceMaster.Common
{
    public class Updater
    {
        #region Const

        private const string AppFiles = "ApplicationFiles";

        private const string NeutralCulture = "neutral";

        #endregion

        #region Instance delegate

        private delegate void RunUpdate();

        #endregion

        #region Instance events

    	/// <summary>
        /// Event notices of finishe update process.
        /// </summary>
        public event EventHandler UpdaterComplited;

        #endregion

        #region Insatnce fileds

        /// <summary>
        /// 
        /// </summary>
        private RunUpdate _runner;

    	private readonly BindingList<ManifestOperation> _operationList;

        readonly ManifestOperation _createFolderTree = new ManifestOperation(Resources.Operation_CreateFolderTree);
        readonly ManifestOperation _applicationManifestProcessing = new ManifestOperation(Resources.Operation_ApplicationManifestProcessing);
        readonly ManifestOperation _applicationManifestSaving = new ManifestOperation(Resources.Operation_ApplicationManifestSaving);
        readonly ManifestOperation _deployManifestProcessing = new ManifestOperation(Resources.Operation_DeployManifestProcessing);
        readonly ManifestOperation _deployManifestSaving = new ManifestOperation(Resources.Operation_DeployManifestSaving);
        readonly ManifestOperation _finish = new ManifestOperation(Resources.Operation_Finish);

        #endregion

		#region .ctors

		public Updater()
		{
			_operationList = new BindingList<ManifestOperation>(new List<ManifestOperation>
                                                                    {
                                                                        _createFolderTree,
                                                                        _applicationManifestProcessing,
                                                                        _applicationManifestSaving,
                                                                        _deployManifestProcessing,
                                                                        _deployManifestSaving,
                                                                        _finish
                                                                    });
		}

		#endregion

        #region Instane properties

        public BindingList<ManifestOperation> OperationList
        {
            get
            {
                return _operationList;
            }
        }


        #endregion

        #region Instance methods

        #region Auxiliary methods

        /// <summary>
        /// Starts the manifest update.
        /// </summary>
        public void BeginUpdate()
        {
            _runner = new RunUpdate(Update);
            _runner.BeginInvoke(UpdateCallback, null);
        }

        /// <summary>
        /// Calls whent the updater was finished.
        /// </summary>
        /// <param name="result"></param>
        private void UpdateCallback(IAsyncResult result)
        {
            if (result != null)
            {
                RaiseUpdaterComlited(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Updates the target manifest.
        /// </summary>
        public void Update()
        {
            try
            {
                #region Create directories tree

                _createFolderTree.Start();

                string rootPath = WizardSettings.Current.Manifest.DestinationFilesPath;

                //load application folder
                string appFilesPath = Path.Combine(rootPath, AppFiles);
                DirectoryInfo appFilesDir = new DirectoryInfo(appFilesPath);
                if (!appFilesDir.Exists)
                {
                    Directory.CreateDirectory(appFilesPath);
                }

                //load version folder
                string applicationFolder = QueryHelper.BuildApplicationFolder(WizardSettings.Current.Manifest.ApplicationName,
                                                                              WizardSettings.Current.Manifest.PrimaryNumber,
                                                                              WizardSettings.Current.Manifest.AdditionalNumber,
                                                                              WizardSettings.Current.Manifest.ConstructionNumber,
                                                                              WizardSettings.Current.Manifest.ReleaseNumber);

                string appVersionPath = Path.Combine(appFilesPath, applicationFolder);
                DirectoryInfo appVersionDir = new DirectoryInfo(appVersionPath);
                if (!appVersionDir.Exists)
                {
                    Directory.CreateDirectory(appVersionPath);
                }

				_createFolderTree.Complit();

                #endregion

                #region Create application manifest

                _applicationManifestProcessing.Start();

                string targetArhitecture = string.Empty;
                string targetApplicationManifestFileName = string.Empty;
                string targetDeployManifestFileName = string.Empty;

                var applicationManifest = new ApplicationManifest();

                //load files
                foreach (ManifestFile file in WizardContext.Files)
                {
                    BaseReference reference = AddFile(file, applicationManifest, appVersionPath);

                    if (file.FileType == FileType.EntryPoint)
                    {
                        var ar = reference as AssemblyReference;
                        if (ar != null)
                        {
                            AssemblyIdentity assemId = ar.AssemblyIdentity;
                            if (assemId != null)
                            {
                                targetArhitecture = assemId.ProcessorArchitecture;
                                applicationManifest.EntryPoint = ar;
                                targetApplicationManifestFileName = Path.GetFileName(file.FileName);
                                targetDeployManifestFileName = Path.GetFileNameWithoutExtension(file.FileName);
                            }
                        }
                    }

                    if (file.FileType == FileType.Config)
                    {
                        applicationManifest.ConfigFile = reference.SourcePath;
                    }
                }

                //set values
                string applicationVersion = QueryHelper.BuildApplicationVersion(WizardSettings.Current.Manifest.PrimaryNumber,
                                                                                WizardSettings.Current.Manifest.AdditionalNumber,
                                                                                WizardSettings.Current.Manifest.ConstructionNumber,
                                                                                WizardSettings.Current.Manifest.ReleaseNumber);

                applicationManifest.AssemblyIdentity.ProcessorArchitecture = targetArhitecture;
                applicationManifest.AssemblyIdentity.Culture = NeutralCulture;
                applicationManifest.AssemblyIdentity.Name = WizardSettings.Current.Manifest.ApplicationName;
                applicationManifest.AssemblyIdentity.Version = applicationVersion;

                applicationManifest.TrustInfo = new TrustInfo { IsFullTrust = true };
                applicationManifest.ResolveFiles();

                applicationManifest.Validate();
                applicationManifest.UpdateFileInfo();

                string applicationManifestFilePath = Path.Combine(appVersionPath,
                                                                  string.Format("{0}.manifest", targetApplicationManifestFileName));

				_applicationManifestProcessing.Complit();

				//save app manifest
            	_applicationManifestSaving.Start();

                ManifestHelper.SaveAndSignManifest(applicationManifest, applicationManifestFilePath,
                                                   WizardSettings.Current.Manifest.KeyPath,
                                                   WizardSettings.Current.Manifest.KeyPassword);
                _applicationManifestSaving.Complit();

                #endregion

                #region Create deploy manifest

                _deployManifestProcessing.Start();

                string deployManifestFilePath = Path.Combine(rootPath,
                                                             string.Format("{0}.application", targetDeployManifestFileName));

                var deployManifest = new DeployManifest
                                         {
                                             Publisher = WizardSettings.Current.Manifest.ApplicationPublisher,
                                             Product = WizardSettings.Current.Manifest.ApplicationName,
                                             Install = true,
                                             UpdateEnabled = false,
                                             MapFileExtensions = true,
                                             AssemblyIdentity =
                                                 {
                                                     ProcessorArchitecture = targetArhitecture,
                                                     Name = WizardSettings.Current.Manifest.ApplicationName,
                                                     Version = applicationVersion
                                                 },
                                             SourcePath = deployManifestFilePath
                                         };

                deployManifest.UpdateEnabled = true;
                deployManifest.UpdateMode = UpdateMode.Foreground;

                ManifestHelper.UpdateDeployManifestAppReference(deployManifest, applicationManifest);

                _deployManifestProcessing.Complit();

                _deployManifestSaving.Start();

                ManifestHelper.SaveAndSignManifest(deployManifest, deployManifestFilePath,
                                                   WizardSettings.Current.Manifest.KeyPath,
                                                   WizardSettings.Current.Manifest.KeyPassword);

                _deployManifestSaving.Complit();

                _finish.Complit();

                #endregion
            }
            catch (Exception ex)
            {
                ExceptionHelper.HandleException(ex);
            }
        }

        /// <summary>
        /// Adds target file to the manifest.
        /// </summary>
        /// <param name="file"></param>
        /// <param name="manifest"></param>
        /// <param name="targetDir"></param>
        /// <returns></returns>
        private BaseReference AddFile(ManifestFile file, ApplicationManifest manifest, string targetDir)
        {
            BaseReference result = null;
            if (file != null && manifest != null)
            {
                string currentDir = Path.GetDirectoryName(file.FileName);
                if (currentDir == targetDir.ToLower())
                {
                    result = ManifestHelper.AddFileInPlace(file.FileName, true, manifest);
                }
                else
                {
                    result = ManifestHelper.AddFileToTargetFolder(file.FileName, targetDir, manifest);
                }
            }
            return result;
        }

    	/// <summary>
        /// Fires the <see cref="UpdaterComplited"/> event.
        /// </summary>
        /// <param name="e"></param>
        private void RaiseUpdaterComlited(EventArgs e)
        {
            EventHandler comlited = UpdaterComplited;
            if (comlited != null) comlited(this, e);
        }

        #endregion

        #endregion
    }
}